using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace Gko.Wpf.Mvvm
{
	public static class PasswordBoxBehavior
	{
		public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached("BindPassword", typeof(bool), typeof(PasswordBoxBehavior), new PropertyMetadata(false, OnBindPasswordChanged));
		
		public static readonly DependencyProperty BoundPassword = DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxBehavior), new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));

		private static readonly DependencyProperty UpdatingPassword = DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxBehavior));

		private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var box = d as PasswordBox;

			// only handle this event when the property is attached to a PasswordBox  
			// and when the BindPassword attached property has been set to true  
			if (box == null || !GetBindPassword(d)) {
				return;
			}

			// avoid recursive updating by ignoring the box's changed event  
			box.PasswordChanged -= HandlePasswordChanged;

			var newPassword = (string)e.NewValue;

			if (!GetUpdatingPassword(box)) {
				box.Password = newPassword;
			}

			box.PasswordChanged += HandlePasswordChanged;
		}

		private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
		{
			// when the BindPassword attached property is set on a PasswordBox,  
			// start listening to its PasswordChanged event  

			var box = dp as PasswordBox;

			if (box == null) {
				return;
			}

			var wasBound = (bool)(e.OldValue);
			var needToBind = (bool)(e.NewValue);

			if (wasBound) {
				box.PasswordChanged -= HandlePasswordChanged;
			}

			if (needToBind) {
				box.PasswordChanged += HandlePasswordChanged;
			}
		}

		private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
		{
			var box = sender as PasswordBox;
			if (box == null) {
				return;
			}

			// set a flag to indicate that we're updating the password  
			SetUpdatingPassword(box, true);
			// push the new password into the BoundPassword property  
			SetBoundPassword(box, box.Password);
			SetUpdatingPassword(box, false);
		}

		/// <exception cref="ArgumentNullException"><c>dp</c> is null.</exception>
		public static void SetBindPassword(DependencyObject dp, bool value)
		{
			if (dp == null) {
				throw new ArgumentNullException("dp");
			}
			dp.SetValue(BindPassword, value);
		}

		/// <exception cref="ArgumentNullException"><c>dp</c> is null.</exception>
		public static bool GetBindPassword(DependencyObject dp)
		{
			if (dp == null) {
				throw new ArgumentNullException("dp");
			}
			return (bool)dp.GetValue(BindPassword);
		}

		/// <exception cref="ArgumentNullException"><c>dp</c> is null.</exception>
		public static string GetBoundPassword(DependencyObject dp)
		{
			if (dp == null) {
				throw new ArgumentNullException("dp");
			}
			return (string)dp.GetValue(BoundPassword);
		}

		/// <exception cref="ArgumentNullException"><c>dp</c> is null.</exception>
		public static void SetBoundPassword(DependencyObject dp, string value)
		{
			if (dp == null) {
				throw new ArgumentNullException("dp");
			}
			dp.SetValue(BoundPassword, value);
		}

		private static bool GetUpdatingPassword(DependencyObject dp)
		{
			return (bool)dp.GetValue(UpdatingPassword);
		}

		private static void SetUpdatingPassword(DependencyObject dp, bool value)
		{
			dp.SetValue(UpdatingPassword, value);
		}
	}
}